Desenvolvimento para Dispositivos Móveis
Aula 06
Prof. Dr. Raulcézar Alves
raulcezar@gmail.com
Google - Firebase
É uma plataforma de backend para construção de aplicações Web, Android e iOS. Ela oferece: base de dados de tempo-real, diferentes APIs, serviço de autenticação e hospedagem. Então, seu App pode se conectar a um projeto no Firebase para usar os serviços citados. O Android Studio facilita essa integração.
Google - Firebase
- Real-time database: suporta dados JSON como forma de estruturar os dados, e todos os usuários conectados a ele recebem atualizações após cada mudança.
- Authentication: pode−se usar diferentes formas de autenticação na sua aplicação, como: anônimo, usuário-senha, e contas de redes sociais.
- Hosting: o backend da aplicação pode ser hospedado nos servidores Firebase, inclusive com conexão segura.
Google - Firebase
Vantagens:
- Fácil e amigável de usar, sem configurações complexas.
- Os dados são de tempo-real, o que significa que cada mudança automaticamente atualiza as informações dos clientes conectados.
- Oferece um painel de controle simples para serviços.
- Existem diversos serviços úteis disponíveis.
Desvantagens:
- O plano grátis é limitado a 50 conexões e 100MB de armazenamento.
Google - Firebase
Site: é necessário uma conta google
Google - Firebase
Console de um projeto: serviços disponíveis
Google - Firebase
Autenticação>>usuários: mostra os usuários cadastrados para logar no seu App.
Google - Firebase
Autenticação>>método de login: tipos de autenticação. Pode ser: anônimo, usuário-senha com autocadastro direto no seu app, ou por conta de redes sociais.
Google - Firebase
Autenticação>>modelos: verificações de cadastro.
Google - Firebase
Autenticação>>modelos: verificações de cadastro.
Google - Firebase
Autenticação>>modelos: verificações de cadastro.
Google - Firebase
Autenticação>>modelos: verificações de cadastro.
Google - Firebase
Autenticação>>modelos: verificações de cadastro.
Google - Firebase
Database: oferece 2 sistemas.
Realtime Database | Cloud Firestore |
Armazena dados como uma grande árvore JSON. | Armazena dados em documentos organizados em coleções. |
Suporte off-line para clientes móveis apenas no iOS e Android. | Suporte off-line para clientes iOS, Android e da Web. |
Consultas avançadas com funcionalidade limitada de classificação e filtragem. | Consultas indexadas com classificação e filtragem compostas. |
Operações básicas de gravação e transação. | Operações em lote atômicas de gravação e transação. |
É um produto maduro. | Está na versão Beta. |
Google - Firebase
Database: armazenamento de dados remoto.
Google - Firebase
Database>>dados: mostra/adiciona/remove dados da estrutura.
Google - Firebase
Database>>regras: permissões de leitura e escrita na estrutura de dados.
Google - Firebase
Database>>backups: por padrão na versão free não há.
Google - Firebase
Database>>uso: estatísticas de dispositivos conectados, espaço de armazenamento, etc.
Google - Firebase
Storage: armazenamento de mídias e dados não estruturados.
Google - Firebase
Hosting: hospedagem de aplicações web.
Google - Firebase
Function: rodar funções de backend com JS no servidor Firebase.
Google - Firebase
ML Kit: algoritmos de Machine Learning (IA) para: reconhecimento de texto, detecção de rostos, leitura de códigos de barras, identificação de imagens e reconhecimento de pontos de referência. Algoritmos já treinados, seu App envia o dado (imagem, texto, etc) e o serviço Firebase retorna a resposta.
Criação de App com autenticação no Firebase
Criação de App com autenticação no Firebase
pré-requisitos
- dispositivo com versão 4.0 ou superior
- Google Play Services 11.0.4 ou superior
- Android Studio versão 1.5 ou superior
- usar Firebase Assistant do Android Studio
- se for usar emulador, certifique-se de criá-lo usando uma imagem que possua a Play Store instalada
Crie um novo projeto no Android Studio
Crie um novo projeto no Android Studio
Crie um novo projeto no Android Studio
Crie um novo projeto no Android Studio
Selecione uma Activity do tipo login. Isso criará toda a estrutura para logar, deslocar e autocadastrar um usuário no Firebase futuramente.
Crie um novo projeto no Android Studio
Selecione uma Activity do tipo login. Isso criará toda a estrutura para logar, deslocar e autocadastrar um usuário no Firebase futuramente.
Depois disso, vá no menu Tools > Firebase e o Firebase Assistant se abrirá, com diversas opções.
Escolha a Authentication.
O método de autenticação será por e-mail e senha. O primeiro passo é conectar seu app ao Firebase, clicando no botão "Connect to Firebase".
Será necessário se conectar a sua conta google. Um site será aberto para logar e dar as permissões.
Será necessário se conectar a sua conta google. Um site será aberto para logar e dar as permissões.
Se tudo aconteceu corretamente, você deve ver a tela de sucesso informando que você já pode voltar ao Android Studio.
Quando voltar ao Android Studio, configure um novo Firebase Project, que basicamente é o seu projeto de backend. Use o mesmo nome do seu projeto e selecione a região Brazil.
Algumas dependências serão baixadas. Depois disso, o Firebase mostrará que está conectado ao Android Studio. Agora adicione a autenticação ao App.
Mais dependências serão instaladas. Aceite as modificações.
Em caso de warnings modifique o arquivo chamado app.
Abra o console do Firebase.
Selecione seu projeto no Firebase.
Abra Desenvolvedor>>Authentication>>Configurar método de login.
Selecione e-mail/senha.
Mande ativar.
Crie uma nova Activity para ser a Main.
Selecione "Launcher" para ela ser lançada ao ligar o App e "Fragment" para termos um menu.
Remova o "Launcher" da activity Login, criada primeiro, no manifest.
Explicando MainActivity.java.
Instância de acesso ao Autenticador Firebase.
Explicando MainActivity.java.
Ao acessar o App, esta activity é acionada e o primeiro método executado é o onCreate.
Explicando MainActivity.java.
Se o usuário estiver logado mostra msg de boas vindas, senão redireciona para a activity de login.
Explicando MainActivity.java.
Esta activity possui um menu, e ao ser criado o adiciona na activity (infla ao ser clicado no canto direito superior).
Explicando MainActivity.java.
Existe uma opção no menu que é "sair". Caso ela seja selecionada, o logout é executado e o usuário é redirecionado para a activity de login.
MainActivity.java
package mobile.pitagoras.uatzap;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
public class MainActivity extends AppCompatActivity {
private FirebaseAuth mAuth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mAuth = FirebaseAuth.getInstance();
FirebaseUser user = mAuth.getCurrentUser();
if (user != null) {
Toast.makeText(getApplicationContext(), "Bem vindo de volta " + user.getEmail() + "!", Toast.LENGTH_LONG).show();
} else {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();
}
}
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_exit) {
mAuth.signOut();
startActivity(new Intent(this, LoginActivity.class));
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Explicando activity_main.xml.
Utiliza um LinearLayout e possui apenas uma área para aparecer uma barra de menu.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</LinearLayout>
Explicando menu_main.xml.
Descreve o menu que aparece na main, com item para sair.
menu_main.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="mobile.pitagoras.uatzap.MainActivity">
<item
android:id="@+id/action_exit"
android:orderInCategory="100"
android:title="Sair"
app:showAsAction="never" />
</menu>
Explicando LoginActivity.java.
Vários métodos foram criados automaticamente por termos escolhido uma activity do tipo login.
Explicando LoginActivity.java.
Para aproveitar o código já existente, vamos modificar o método attemptLogin para que ele sirva para signin ou signup, de acordo com um parâmetro passado à ele.
Explicando LoginActivity.java.
Ele passará a se chamar attemptLoginOrRegister que serve para tentar logar ou se autocadastrar. Isso depende do botão que o usuário irá clicar.
LoginActivity.java
package mobile.pitagoras.uatzap;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import java.util.ArrayList;
import java.util.List;
import static android.Manifest.permission.READ_CONTACTS;
public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> {
private FirebaseAuth mAuth;
private static final int REQUEST_READ_CONTACTS = 0;
private static final String[] DUMMY_CREDENTIALS = new String[]{ "foo@example.com:hello", "bar@example.com:world"};
private UserLoginTask mAuthTask = null;
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
populateAutoComplete();
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLoginOrRegister(false);
return true;
}
return false;
}
});
mAuth = FirebaseAuth.getInstance();
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
mEmailSignInButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLoginOrRegister(false);
}
});
Button mSignUpButton = (Button) findViewById(R.id.email_sign_up_button);
mSignUpButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
attemptLoginOrRegister(true);
}
});
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}
private void populateAutoComplete() {
if (!mayRequestContacts()) {
return;
}
getLoaderManager().initLoader(0, null, this);
}
private boolean mayRequestContacts() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
return true;
}
if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
.setAction(android.R.string.ok, new View.OnClickListener() {
@Override
@TargetApi(Build.VERSION_CODES.M)
public void onClick(View v) {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
});
} else {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
populateAutoComplete();
}
}
}
private void attemptLoginOrRegister(boolean isNewUser) {
if (mAuthTask != null) {
return;
}
mEmailView.setError(null);
mPasswordView.setError(null);
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) {
focusView.requestFocus();
} else {
showProgress(true);
if(isNewUser) {
mAuth.createUserWithEmailAndPassword(email, password).addOnCompleteListener(LoginActivity.this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
showProgress(false);
Toast.makeText(getApplicationContext(), "Usuário cadastrado com sucesso. Agora você pode se autenticar com suas credenciais!", Toast.LENGTH_LONG).show();
}
});
}
else {
try {
mAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(LoginActivity.this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
showProgress(false);
if (task.getResult().getUser() != null) {
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
} else {
Toast.makeText(getApplicationContext(), "Email e/ou senha incorretos.", Toast.LENGTH_LONG).show();
}
}
});
}
catch(Exception e){
e.printStackTrace();
}
}
}
}
private boolean isEmailValid(String email) {
return email.contains("@");
}
private boolean isPasswordValid(String password) {
return password.length() > 4;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
List<String> emails = new ArrayList<>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
emails.add(cursor.getString(ProfileQuery.ADDRESS));
cursor.moveToNext();
}
addEmailsToAutoComplete(emails);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) { }
private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
ArrayAdapter<String> adapter =
new ArrayAdapter<>(LoginActivity.this,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
mEmailView.setAdapter(adapter);
}
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};
int ADDRESS = 0;
int IS_PRIMARY = 1;
}
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
private final String mEmail;
private final String mPassword;
UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}
@Override
protected Boolean doInBackground(Void... params) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
return false;
}
for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
return pieces[1].equals(mPassword);
}
}
return true;
}
@Override
protected void onPostExecute(final Boolean success) {
mAuthTask = null;
showProgress(false);
if (success) {
finish();
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
@Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}
}
Explicando activity_login.xml.
Possui um campo para e-mail e outro para senha, e dois botões, um para logar e outro pra se registrar. Ambos enviam os dados para o Firebase.
activity_login.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".LoginActivity">
<ProgressBar
android:id="@+id/login_progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:visibility="gone" />
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/email_login_form"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Senha"
android:imeActionId="@+id/login"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/email_sign_in_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/colorPrimaryDark"
android:textColor="@android:color/white"
android:text="Entrar"
android:textStyle="bold" />
<Button
android:id="@+id/email_sign_up_button"
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Registrar"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
</LinearLayout>
pacotes do seu projeto
Verifique se o caminho dos pacotes estão corretos. Este código está em package mobile.pitagoras.uatzap, e deve ser trocado pelo local do seu código.
Outros arquivos
Verifique também se os seguintes arquivos estão corretos.
build.gradle (Module: app)
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "mobile.pitagoras.uatzap"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//noinspection GradleCompatible
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.google.firebase:firebase-auth:11.6.0'
implementation 'com.google.firebase:firebase-core:11.6.0'
implementation 'com.google.firebase:firebase-database:11.6.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply plugin: 'com.google.gms.google-services'
build.gradle (Project: UatZap)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'com.google.gms:google-services:3.1.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="mobile.pitagoras.uatzap">
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".LoginActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Autoregistro App+Firebase
Autoregistro App+Firebase